package org.ym.tools;

import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

/**
 * 压缩
 *
 * @author ym
 */
public class ZipTool {

    public static final String CHARSET_NAME_GBK = "GBK";

    private ZipTool() {
    }

    /**
     * 压缩,删除原文件
     *
     * @param waitCompressPath 待压缩文件/文件夹
     * @return 压缩文件路径
     */
    public static Path zip(Path waitCompressPath) {
        return zip(waitCompressPath, true);
    }

    /**
     * 压缩
     *
     * @param waitCompressPath         待压缩文件/文件夹
     * @param isDeleteWaitCompressFile 压缩完成后是否删除原压缩目录
     * @return 压缩文件路径
     */
    public static Path zip(Path waitCompressPath, boolean isDeleteWaitCompressFile) {
        return zip(waitCompressPath, null, null, null, isDeleteWaitCompressFile);
    }

    /**
     * 压缩,删除原文件
     *
     * @param waitCompressPath 待压缩文件/文件夹
     * @param password         加密密码
     * @return 压缩文件路径
     */
    public static Path zip(Path waitCompressPath, String password) {
        return zip(waitCompressPath, password, null, null, true);
    }

    /**
     * 压缩
     *
     * @param waitCompressPath         待压缩文件/文件夹
     * @param isDeleteWaitCompressFile 压缩完成后是否删除原压缩目录
     * @param password                 加密密码
     * @return 压缩文件路径
     */
    public static Path zip(Path waitCompressPath, String password, boolean isDeleteWaitCompressFile) {
        return zip(waitCompressPath, password, null, null, isDeleteWaitCompressFile);
    }

    /**
     * 压缩
     *
     * @param waitCompressPath         待压缩文件/文件夹
     * @param isDeleteWaitCompressFile 压缩完成后是否删除原压缩目录
     * @param password                 加密密码
     * @param suffix                   压缩后采用新后缀
     * @return 压缩文件路径
     */
    public static Path zip(Path waitCompressPath, String password, String suffix, boolean isDeleteWaitCompressFile) {
        return zip(waitCompressPath, password, suffix, null, isDeleteWaitCompressFile);
    }

    /**
     * 压缩
     *
     * @param waitCompressPath         待压缩文件/文件夹
     * @param password                 加密密码
     * @param suffix                   压缩后采用新后缀
     * @param isDeleteWaitCompressFile 压缩完成后是否删除原压缩目录
     * @return 压缩文件路径
     */
    public static Path zip(Path waitCompressPath, String password, String suffix, String comment, boolean isDeleteWaitCompressFile) {
        if (!waitCompressPath.toFile().exists()) {
            return null;
        }
        if (StringUtils.isBlank(suffix)) {
            suffix = "zip";
        }
        String zipFileName;
        if (waitCompressPath.toFile().isDirectory()) {
            zipFileName = StringTool.concatStr(waitCompressPath.toString(), StringPool.DOT, suffix);
        } else {
            zipFileName = StringTool.concatStr(FileTool.getStrWithoutExt(waitCompressPath.toString()), StringPool.DOT, suffix);
        }
        Path zipFilePath = Paths.get(zipFileName);
        ArrayList<Path> fileArrayList = new ArrayList<>();
        fileArrayList.add(waitCompressPath);
        zip(fileArrayList, zipFilePath, password, comment, isDeleteWaitCompressFile);
        return zipFilePath;
    }

    /**
     * 压缩
     *
     * @param waitCompressPathList     待压缩文件集合
     * @param zipFilePath              zip文件
     * @param password                 密码
     * @param comment                  备注
     * @param isDeleteWaitCompressFile 压缩后是否删除待压缩文件
     */
    public static void zip(List<Path> waitCompressPathList, Path zipFilePath, String password, String comment, boolean isDeleteWaitCompressFile) {
        if (waitCompressPathList == null || waitCompressPathList.isEmpty()) {
            return;
        }
        try {
            ZipParameters zipParameters = new ZipParameters();
            zipParameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);
            zipParameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
            if (StringUtils.isNotBlank(password)) {
                zipParameters.setEncryptFiles(true);
                zipParameters.setEncryptionMethod(Zip4jConstants.ENC_METHOD_STANDARD);
                zipParameters.setPassword(password);
            }
            if (zipFilePath.toFile().exists()) {
                FileUtils.forceDelete(zipFilePath.toFile());
            }
            ZipFile zipFile = new ZipFile(zipFilePath.toFile());
            // 设置文件名编码，在GBK系统中需要设置
            zipFile.setFileNameCharset(CHARSET_NAME_GBK);
            ArrayList<File> zipFileList = new ArrayList<>();
            for (Path path : waitCompressPathList) {
                if (!path.toFile().exists()) {
                    throw new ZipRuntimeException(StringTool.concatStr("未找到【", path.toString(), "】文件！"));
                }
                zipFileList.add(path.toFile());
            }
            zipFile.addFiles(zipFileList, zipParameters);
            if (StringUtils.isNotBlank(comment)) {
                zipFile.setComment(comment);
            }
            if (isDeleteWaitCompressFile) {
                for (Path path : waitCompressPathList) {
                    FileUtils.forceDelete(path.toFile());
                }
            }
        } catch (ZipException zipException) {
            throw new ZipRuntimeException(StringTool.concatStr("【", zipFilePath.toString(), "】文件创建ZipFile出错！"), zipException);
        } catch (IOException e) {
            throw new ZipRuntimeException(e);
        }
    }

    /**
     * 解压
     *
     * @param zipFilePath 压缩包路径
     * @param destPath    解压路径
     */
    public static List<Path> unZip(Path zipFilePath, Path destPath) {
        return unZip(zipFilePath, destPath, null);
    }

    /**
     * 解压
     *
     * @param zipFilePath 压缩包路径
     * @param destPath    解压路径
     * @param password    解压密码
     */
    public static List<Path> unZip(Path zipFilePath, Path destPath, String password) {
        try {
            if (!zipFilePath.toFile().exists()) {
                throw new ZipRuntimeException(StringTool.concatStr("未找到压缩文件【", zipFilePath.toString(), "】！"));
            }
            if (!destPath.toFile().isDirectory()) {
                throw new ZipRuntimeException(StringTool.concatStr("解压路径【", destPath.toString(), "】不是一个文件夹！"));
            }
            if (destPath.toFile().exists()) {
                destPath.toFile().mkdirs();
            }
            ZipFile zipFile = new ZipFile(zipFilePath.toFile());
            // 设置文件名编码，在GBK系统中需要设置
            zipFile.setFileNameCharset(CHARSET_NAME_GBK);
            if (zipFile.isEncrypted()) {
                zipFile.setPassword(password);
            }
            List<Path> extractFilePathList = new ArrayList<>();
            List<?> fileHeaderList = zipFile.getFileHeaders();
            for (Object o : fileHeaderList) {
                FileHeader fileHeader = (FileHeader) o;
                zipFile.extractFile(fileHeader, destPath.toString());
                extractFilePathList.add(Paths.get(destPath.toString(),fileHeader.getFileName()));
            }
            return extractFilePathList;
        } catch (ZipException e) {
            throw new ZipRuntimeException(e);
        }
    }

    /**
     * 压缩错误
     */
    private static class ZipRuntimeException extends RuntimeException {
        public ZipRuntimeException(String message) {
            super(message);
        }

        public ZipRuntimeException(Throwable cause) {
            super(cause);
        }

        public ZipRuntimeException(String message, Throwable cause) {
            super(message, cause);
        }
    }
}
